﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Input;
using System.IO;
using System.IO.IsolatedStorage;

namespace DungDungShit
{
	public class MapContainer
	{
		private ScreenType havingScreen;
		private ScreenManager screenManager;
		private int slotNumber;
		public int SlotNumber { get { return slotNumber; } }

        Player player = null;
        Tile[,] map = null;


        //
        Dictionary<IntVector2, Room> roomList = new Dictionary<IntVector2, Room>();
        public IntVector2 RoomPosition = new IntVector2(0, 0);
        List<Tile[,]> mapList = new List<Tile[,]>();
        IntVector2[] playerCollision = new IntVector2[4];
        public bool isPaused = false;

        public Vector2 PlayerPosition { get { return new Vector2(player.Position.X * Sprite.TileSize.X / 2 + player.SubPosition.X, 
            player.Position.Y * Sprite.TileSize.Y / 2 + player.SubPosition.Y); } }
        public IntVector2 TilePosition { get { return new IntVector2(((int)(PlayerPosition.X + 128) / 128) - 1, ((int)(PlayerPosition.Y + 128) / 128) - 1); } }
        public Rectangle PlayerRectangle { get { return new Rectangle(-32, -32, 64, 32); } }
        public int PlayerHP { get { return player.HP; } }
        public int PlayerScore { get { return player.Score; } }
        public DateTime PlayerTime { get { return player.LimitTime; } }
        public int PlayerCapacity { get { return player.Capacity; } }
        
        IntVector2 calcTilePosition(int x, int y) { return new IntVector2(x / 128, y / 128); }

		public int mapWidth = 10;
		public int mapHeight = 10;

		private static readonly IntVector2 defaultFrameSize = new IntVector2(Sprite.TileSize.X, Sprite.TileSize.Y);
		private static readonly IntVector2 defaultSheetSize = new IntVector2(1, 1);

		private readonly Direction[] directionList = new Direction[] { Direction.None, Direction.Left, Direction.Right, Direction.Up, Direction.Down };

        /// 이 함수에는 슬픈 전설이 있어
        public void DrawRescued(SpriteBatch spriteBatch)
        {
            int i = 0;
            foreach (Person p in player.Rescued)
            {
                spriteBatch.Draw(p.Texture, new Vector2(50*i, 240), new Rectangle(128 * p.Type, 0, 128, 128),
                    Color.White, 0, new Vector2(0, 0), 0.75f, SpriteEffects.None, 0f);
                i++;
            }
        }
        public string PrintFinalResult(bool death)
        {
            return player.FinalResult(death);
        }
        
        public MapContainer(ScreenManager screenManager, ScreenType havingScreen, int slotNumber)
		{
			this.screenManager = screenManager;
			this.havingScreen = havingScreen;
			this.slotNumber = slotNumber;
            IntVector2 playerFrameSize = new IntVector2(128, 128);
            IntVector2 playerSheetSize = new IntVector2(8, 1);

			player = new Player(
				screenManager.GetTextureFromChar('s'),
				screenManager.GetTextureFromChar('j'),
				screenManager.GetTextureFromChar('l'),
				screenManager.GetTextureFromChar('i'),
				screenManager.GetTextureFromChar('k'),
				IntVector2.Zero,
				playerFrameSize,
				playerSheetSize);

            SetPlayerPosition(new IntVector2(5, 5));
            //플레이어-벽 충돌범위 설정
            playerCollision[0] = new IntVector2(-25, -50); //Top-Left
            playerCollision[1] = new IntVector2(25, -50); //Top-Right
            playerCollision[2] = new IntVector2(-25, 0); //Bottom-Left
            playerCollision[3] = new IntVector2(25, 0); //Bottom-Right
			
            InitializeMap();
		}


        /// <summary>
        /// 게임플레이 중 일어나는 모든 업데이트를 여기서 행한다.
        /// </summary>
        /// <param name="gameTime"></param>
        /// <param name="otherScreenHasFocus"></param>
        /// <param name="coveredByOtherScreen"></param>
        /// <param name="inputState"></param>
		public void Update(GameTime gameTime, bool otherScreenHasFocus, bool coveredByOtherScreen, InputState inputState = null)
		{
            if (havingScreen == ScreenType.Game && !isPaused)
            {
                if (inputState == null) return;

                KeyboardState keyboardState = inputState.CurrentKeyboardState,
                    prevKeyBoardState = inputState.LastKeyboardState;

                Direction direction = new Direction(new IntVector2(0, 0));
                if (keyboardState.IsKeyDown(Keys.Left)) direction.X += -1;
                if (keyboardState.IsKeyDown(Keys.Right)) direction.X += 1;
                if (keyboardState.IsKeyDown(Keys.Up)) direction.Y += -1;
                if (keyboardState.IsKeyDown(Keys.Down)) direction.Y += 1;

                GenerateMap();
                SlideMap();
                
                player.Velocity = WellCalculatedVelocity(player, direction);
                player.SelfDirection = new Direction(player.Velocity);
                player.Update(gameTime);

                Damage();
                Rescue();
                
                foreach (Tile[,] m in mapList)
                {
                    foreach (Tile t in m)
                    {
                        t.Update(gameTime);
                    }
                }
            }
		}
        public bool isEscapeTile()
        {
            Tile ang;
            if (TilePosition.X < 0)
                ang = mapList[3][TilePosition.X + mapWidth, TilePosition.Y];
            else if (TilePosition.X >= mapWidth)
                ang = mapList[5][TilePosition.X - mapWidth, TilePosition.Y];
            else if (TilePosition.Y < 0)
                ang = mapList[7][TilePosition.X, TilePosition.Y + mapHeight];
            else if (TilePosition.Y >= mapHeight)
                ang = mapList[7][TilePosition.X, TilePosition.Y - mapHeight];
            else
                ang = map[TilePosition.X, TilePosition.Y];
            return ang is EscapeTile;
        }
        private void Rescue()
        {
            Tile ang;
            if (TilePosition.X < 0)
                ang = mapList[3][TilePosition.X + mapWidth, TilePosition.Y];
            else if (TilePosition.X >= mapWidth)
                ang = mapList[5][TilePosition.X - mapWidth, TilePosition.Y];
            else if (TilePosition.Y < 0)
                ang = mapList[7][TilePosition.X, TilePosition.Y + mapHeight];
            else if (TilePosition.Y >= mapHeight)
                ang = mapList[7][TilePosition.X, TilePosition.Y - mapHeight];
            else
                ang = map[TilePosition.X, TilePosition.Y];
            if (ang is PersonTile)
            {
                PersonTile tile = ang as PersonTile;
                if (!tile.IsEmpty && !player.isFull())
                {
                    Person rescued = tile.RescuePerson();
                    player.Rescue(rescued);
                }
            }
            else if (ang is EscapeTile)
            {
                EscapeTile tile = ang as EscapeTile;
                if (player.Rescued.Count > 0)
                {
                    player.Release();
                }
            }
        }
        private void Damage()
        {
            Tile ang;
            if (TilePosition.X < 0)
                ang = mapList[3][TilePosition.X + mapWidth, TilePosition.Y];
            else if (TilePosition.X >= mapWidth)
                ang = mapList[5][TilePosition.X - mapWidth, TilePosition.Y];
            else if (TilePosition.Y < 0)
                ang = mapList[7][TilePosition.X, TilePosition.Y + mapHeight];
            else if (TilePosition.Y >= mapHeight)
                ang = mapList[7][TilePosition.X, TilePosition.Y - mapHeight];
            else
                ang = map[TilePosition.X, TilePosition.Y];
            if (ang is SpikeTile)
            {
                SpikeTile tile = ang as SpikeTile;
                if (tile.GivesDamage())
                    player.GetDamage();
            }
            else if (ang is FireTile)
            {
                FireTile tile = ang as FireTile;
                if (tile.GivesDamage())
                    player.GetDamage();
            }
        }
		
		/// <summary>
		/// NonTile이 자신이 어떤 방향으로 움직이려할 때 실제로 움직이게 되는 속도를 반환한다.
		/// 즉, 타일이나 벽 등의 영향을 고려한다.
		/// </summary>
		/// <param name="nonTile">NonTile</param>
		/// <param name="direction">움직이려는 방향</param>
		/// <returns>실제로 움직이게 되는 속도</returns>
		private IntVector2 WellCalculatedVelocity(NonTile nonTile, Direction direction)
		{
            IntVector2 velocity = nonTile.Velocity;
            velocity = direction * nonTile.Speed;

            if (!CanGoToTheDirection(nonTile, direction))
            {
                if (!CanGoToTheDirection(nonTile, new IntVector2(direction.X, 0)))
                {
                    if (!CanGoToTheDirection(nonTile, new IntVector2(0, direction.Y)))
                    {
                        velocity = IntVector2.Zero;
                    }
                    else
                        velocity = new IntVector2(0, velocity.Y);
                }
                else
                    velocity = new IntVector2(velocity.X, 0);
            }

			return velocity;
		}

		private bool OutOfBounds(IntVector2 i)
		{
			return i.X < 0 || i.X >= mapWidth || i.Y < 0 || i.Y >= mapHeight;
		}

		private bool CanGoToTheDirection(NonTile nonTile, IntVector2 nextDirection)
		{
            if (nonTile is Player)
            {
                for (int i = 0; i < 4; i++)
                {
                    if(CanGoToTheDirection(nonTile, playerCollision[i], nextDirection))
                        continue;
                    else
                        return false;
                }
            }
            return true;
		}

        private bool CanGoToTheDirection(NonTile nonTile, IntVector2 position, IntVector2 nextDirection)
        {
            IntVector2 next = calcTilePosition((int)PlayerPosition.X + position.X + nextDirection.X * nonTile.Speed, (int)PlayerPosition.Y + position.Y + nextDirection.Y * nonTile.Speed);
            if (next.Equals(TilePosition))
                return true;
            if (next.X < 0)
                return mapList[3][next.X + mapWidth, next.Y].Enter(nonTile, nextDirection);
            if (next.X >= mapWidth)
                return mapList[5][next.X - mapWidth, next.Y].Enter(nonTile, nextDirection);
            if (next.Y < 0)
                return mapList[7][next.X, next.Y + mapHeight].Enter(nonTile, nextDirection);
            if (next.Y >= mapHeight)
                return mapList[1][next.X, next.Y - mapHeight].Enter(nonTile, nextDirection);

            return map[next.X, next.Y].Enter(nonTile, nextDirection);
        }

        Vector2 rumble = new Vector2(0, 0);
        public void Rumble(int time) {
            if (time > 0)
            {
                int n = 7;
                int fuck = ScreenManager.r.Next(n) - n / 2;
                rumble = new Vector2(fuck, fuck);
            }
        }
        public void notRumble()
        {
            rumble = new Vector2(0, 0);
        }
		public void Draw(GameTime gameTime, SpriteBatch spriteBatch, Vector2 origin, float scale = 1f)
		{
            Matrix ang = Matrix.CreateTranslation(GameScreen.mapViewWidth / 2, GameScreen.mapViewHeight / 2, 0);

			spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.AlphaBlend, null, null, null, null, ang);
			
            for (int i = 0; i < 9; i++)
            {
                if (i == 4)
                    continue;
                IntVector2 mapLocation = new IntVector2((int)((i % 3 - 1) * mapWidth * Sprite.TileSize.X), (int)((-(int)(i / 3) + 1) * mapHeight * Sprite.TileSize.Y));
                foreach (Tile t in mapList[8-i])
                {
                    t.Draw(gameTime, spriteBatch, origin + mapLocation + rumble);
                }
            }
            foreach (Tile t in map)
            {
                t.Draw(gameTime, spriteBatch, origin - new IntVector2(0, 0) + rumble);
            }
            spriteBatch.End();

            spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.AlphaBlend, null, null, null, null, ang);
            player.Draw(gameTime, spriteBatch, origin + new IntVector2(64, 112));
            //spriteBatch.Draw(screenManager.GetTextureFromChar(' '), PlayerRectangle, Color.Red);
			spriteBatch.End();
		}
        
        public void SetPlayerPosition(IntVector2 v)
        {
            if (player != null)
            {
                if (!(v.X >= 0 && v.X < mapWidth && v.Y >= 0 && v.Y < mapHeight))
                    v = IntVector2.Zero;
                player.Position = v * 2;
            }
        }
		
        // TODO:
        public void SetMapTile(IntVector2 v, char tileKind, int speed = 0)
		{
            IntVector2 wallSheetSize = new IntVector2(1, 1);  // 벽 타일 움직임 세트 크기 : 6x1
			IntVector2 spikeSheetSize = new IntVector2(16, 1);
            IntVector2 fireSheetSize = new IntVector2(3, 1);
			if (!(v.X >= 0 && v.X < map.GetLength(0) && v.Y >= 0 && v.Y < map.GetLength(1)))
				return;
			Texture2D texture = screenManager.GetTextureFromChar(tileKind);

			switch (tileKind)
            {
                case 'E':
                    map[v.X, v.Y] = new EscapeTile(texture, v, defaultFrameSize, defaultSheetSize);
                    break;
                case '@':
                    int tid = ScreenManager.r.Next(4);
                    map[v.X, v.Y] = new PersonTile(texture, screenManager.PeopleTexture(tid), v, defaultFrameSize, defaultSheetSize, tid);
                    break;
                case '$':
                    map[v.X, v.Y] = new FireTile(texture, v, defaultFrameSize, fireSheetSize);
                    break;
                case '^':
                    map[v.X, v.Y] = new SpikeTile(texture, v, defaultFrameSize, spikeSheetSize);
                    break;
				case '1':
					map[v.X, v.Y] = new WallTile(texture, v, defaultFrameSize, wallSheetSize);
					break;
				default:
					map[v.X, v.Y] = new NormalTile(texture, v, defaultFrameSize, defaultSheetSize);
					break;
			}
		}

		/// <summary>
		/// isolatedStorage로부터 맵을 불러온다.
		/// </summary>
		private void Load()
		{
#if WINDOWS
			IsolatedStorageFile gameStorage = IsolatedStorageFile.GetUserStoreForDomain();
#else
			IsolatedStorageFile savegameStorage = IsolatedStorageFile.GetUserStoreForApplication();
#endif
			// open isolated storage, and write the savefile.
			if (gameStorage.FileExists("" + slotNumber + ".map"))
			{
				IsolatedStorageFileStream fs =
					gameStorage.OpenFile("" + slotNumber + ".map", System.IO.FileMode.Open, System.IO.FileAccess.Read);
				if (fs != null)
				{
					StreamReader reader = new StreamReader(fs);

					LoadMap(reader);

					reader.Close();
					fs.Close();
				}
				gameStorage.Dispose();
			}
			else
			{
				gameStorage.Dispose();
				Load();
			}
		}
		/// <summary>
		/// 파일로부터 맵을 불러온다.
		/// </summary>
		public void LoadFromFile(string filename)
		{
			FileStream fs = new FileStream(filename, FileMode.Open);
			if (fs != null)
			{
				StreamReader reader = new StreamReader(fs);

				LoadMap(reader);

				reader.Close();
				fs.Close();
			}
		}
		/// <summary>
		/// isolatedStorage에 맵을 저장한다.
		/// </summary>
		private void LoadMap(StreamReader reader)
		{
			String[] size;
			int i, j;

			size = reader.ReadLine().Split(' ');
			mapWidth = int.Parse(size[0]);
			mapHeight = int.Parse(size[1]);

			// tiles
			map = new Tile[mapWidth, mapHeight];
			for (j = 0; j < mapHeight; j++)
			{
				String line = reader.ReadLine();
				for (i = 0; i < mapWidth; i++)
				{
					SetMapTile(new IntVector2(i, j), i < line.Length ? line[i] : ' ');
				}
			}
		}
		public override string ToString()
		{
			MemoryStream memoryStream = new MemoryStream();
			StreamWriter writer = new StreamWriter(memoryStream);
			writer.Flush();
			memoryStream.Position = 0;
			StreamReader reader = new StreamReader(memoryStream);
			string s = reader.ReadToEnd();
			writer.Close();
			reader.Close();
			memoryStream.Close();
			return s;
		}

        //여기도 노나메가 삽질함
        public void GenerateMap()
        {
            if (!roomList.ContainsKey(new IntVector2(RoomPosition.X - 3, RoomPosition.Y))) //Left
            {
                IntVector2 currentIndex = roomList[RoomPosition].unitIndex;
                roomList = (new Dungeon(roomList, screenManager)).GetGeneratedMap(new Rectangle((currentIndex.X - 1) * Dungeon.unitMazeWidth, (currentIndex.Y) * Dungeon.unitMazeHeight, Dungeon.unitMazeWidth, Dungeon.unitMazeHeight), currentIndex + new IntVector2(-1, 0));
            }
            if (!roomList.ContainsKey(new IntVector2(RoomPosition.X + 3, RoomPosition.Y))) //Right
            {
                IntVector2 currentIndex = roomList[RoomPosition].unitIndex;
                roomList = (new Dungeon(roomList, screenManager)).GetGeneratedMap(new Rectangle((currentIndex.X + 1) * Dungeon.unitMazeWidth, (currentIndex.Y) * Dungeon.unitMazeHeight, Dungeon.unitMazeWidth, Dungeon.unitMazeHeight), currentIndex + new IntVector2(1, 0));
            }
            if (!roomList.ContainsKey(new IntVector2(RoomPosition.X, RoomPosition.Y - 3))) //Up
            {
                IntVector2 currentIndex = roomList[RoomPosition].unitIndex;
                roomList = (new Dungeon(roomList, screenManager)).GetGeneratedMap(new Rectangle((currentIndex.X) * Dungeon.unitMazeWidth, (currentIndex.Y - 1) * Dungeon.unitMazeHeight, Dungeon.unitMazeWidth, Dungeon.unitMazeHeight), currentIndex + new IntVector2(0, -1));
            }
            if (!roomList.ContainsKey(new IntVector2(RoomPosition.X, RoomPosition.Y + 3))) //Down
            {
                IntVector2 currentIndex = roomList[RoomPosition].unitIndex;
                roomList = (new Dungeon(roomList, screenManager)).GetGeneratedMap(new Rectangle((currentIndex.X) * Dungeon.unitMazeWidth, (currentIndex.Y + 1) * Dungeon.unitMazeHeight, Dungeon.unitMazeWidth, Dungeon.unitMazeHeight), currentIndex + new IntVector2(0, +1));
            }
            if (!roomList.ContainsKey(new IntVector2(RoomPosition.X - 2, RoomPosition.Y - 2))) //Up-Left
            {
                IntVector2 currentIndex = roomList[RoomPosition].unitIndex;
                roomList = (new Dungeon(roomList, screenManager)).GetGeneratedMap(new Rectangle((currentIndex.X - 1) * Dungeon.unitMazeWidth, (currentIndex.Y - 1) * Dungeon.unitMazeHeight, Dungeon.unitMazeWidth, Dungeon.unitMazeHeight), currentIndex + new IntVector2(-1, -1));
            }
            if (!roomList.ContainsKey(new IntVector2(RoomPosition.X + 2, RoomPosition.Y - 2))) //Up-Right
            {
                IntVector2 currentIndex = roomList[RoomPosition].unitIndex;
                roomList = (new Dungeon(roomList, screenManager)).GetGeneratedMap(new Rectangle((currentIndex.X + 1) * Dungeon.unitMazeWidth, (currentIndex.Y - 1) * Dungeon.unitMazeHeight, Dungeon.unitMazeWidth, Dungeon.unitMazeHeight), currentIndex + new IntVector2(+1, -1));
            }
            if (!roomList.ContainsKey(new IntVector2(RoomPosition.X - 2, RoomPosition.Y + 2))) //Down-Left
            {
                IntVector2 currentIndex = roomList[RoomPosition].unitIndex;
                roomList = (new Dungeon(roomList, screenManager)).GetGeneratedMap(new Rectangle((currentIndex.X - 1) * Dungeon.unitMazeWidth, (currentIndex.Y + 1) * Dungeon.unitMazeHeight, Dungeon.unitMazeWidth, Dungeon.unitMazeHeight), currentIndex + new IntVector2(-1, +1));
            }
            if (!roomList.ContainsKey(new IntVector2(RoomPosition.X + 2, RoomPosition.Y + 2))) //Down-Right
            {
                IntVector2 currentIndex = roomList[RoomPosition].unitIndex;
                roomList = (new Dungeon(roomList, screenManager)).GetGeneratedMap(new Rectangle((currentIndex.X + 1) * Dungeon.unitMazeWidth, (currentIndex.Y + 1) * Dungeon.unitMazeHeight, Dungeon.unitMazeWidth, Dungeon.unitMazeHeight), currentIndex + new IntVector2(+1, +1));
            }
        }
        public void InitializeMap()
        {
            roomList = (new Dungeon(new Dictionary<IntVector2,Room>(), screenManager)).GetGeneratedMap(new Rectangle(0, 0, Dungeon.unitMazeWidth, Dungeon.unitMazeHeight), new IntVector2(0, 0), true);
            IntVector2 center = new IntVector2((int)(Dungeon.unitMazeWidth/2), (int)(Dungeon.unitMazeHeight/2));

            mapList = new List<Tile[,]>();
            mapList.Add(roomList[new IntVector2(center.X - 1, center.Y + 1)].map);
            mapList.Add(roomList[new IntVector2(center.X, center.Y + 1)].map);
            mapList.Add(roomList[new IntVector2(center.X + 1, center.Y + 1)].map);
            mapList.Add(roomList[new IntVector2(center.X - 1, center.Y)].map);
            mapList.Add(roomList[new IntVector2(center.X, center.Y)].map);
            mapList.Add(roomList[new IntVector2(center.X + 1, center.Y)].map);
            mapList.Add(roomList[new IntVector2(center.X - 1, center.Y - 1)].map);
            mapList.Add(roomList[new IntVector2(center.X, center.Y - 1)].map);
            mapList.Add(roomList[new IntVector2(center.X + 1, center.Y - 1)].map);
            map = mapList[4];
            RoomPosition = center;
        }

        //<카메라>가 각각의 해당하는 방향으로 이동
        public void SlideMapDown()
        {
            for (int i = 8; i > 2; i--)
            {
                mapList[i] = mapList[i - 3];
            }
            for (int i = 0; i < 3; i++)
            {
                mapList[i] = roomList[new IntVector2(RoomPosition.X - 1 + i, RoomPosition.Y + 2)].map;
            }
            map = mapList[4];
            RoomPosition += new IntVector2(0, 1);
        }
        public void SlideMapUp()
        {
            for (int i = 0; i < 6; i++)
            {
                mapList[i] = mapList[i + 3];
            }
            for (int i = 6; i < 9; i++)
            {
                mapList[i] = roomList[new IntVector2(RoomPosition.X - 7 + i, RoomPosition.Y - 2)].map;
            }
            map = mapList[4];
            RoomPosition += new IntVector2(0, -1);
        }
        public void SlideMapLeft()
        {
            for (int i = 0; i < 3; i++)
            {
                mapList[i * 3 + 2] = mapList[i * 3 + 1];
                mapList[i * 3 + 1] = mapList[i * 3];
                mapList[i * 3] = roomList[new IntVector2(RoomPosition.X - 2, RoomPosition.Y + 1 - i)].map;
            }
            map = mapList[4];
            RoomPosition += new IntVector2(-1, 0);
        }
        public void SlideMapRight()
        {
            for (int i = 0; i < 3; i++)
            {
                mapList[i * 3] = mapList[i * 3 + 1];
                mapList[i * 3 + 1] = mapList[i * 3 + 2];
                mapList[i * 3 + 2] = roomList[new IntVector2(RoomPosition.X + 2, RoomPosition.Y + 1 - i)].map;
            }
            map = mapList[4];
            RoomPosition += new IntVector2(1, 0);
        }
        //맵 나가면 알아서 이동
        public void SlideMap()
        {
            if (TilePosition.X < 0)
            {
                SlideMapLeft();
                player.Position.X += mapWidth * 2;
            }
            else if (TilePosition.X >= mapWidth)
            {
                SlideMapRight();
                player.Position.X -= mapWidth * 2;
            }
            else if (TilePosition.Y < 0)
            {
                SlideMapUp();
                player.Position.Y += mapHeight * 2;
            }
            else if (TilePosition.Y >= mapHeight)
            {
                SlideMapDown();
                player.Position.Y -= mapHeight * 2;
            }
        }

	}

}
